home *** CD-ROM | disk | FTP | other *** search
/ Australian Personal Computer 2000 July / CD 3 / redhat-6.2.iso / RedHat / instimage / usr / lib / python1.5 / site-packages / gettext.py < prev    next >
Encoding:
Python Source  |  2000-02-16  |  8.6 KB  |  321 lines

  1. """This module allows python programs to use GNU gettext message catalogs.
  2.  
  3. Author: James Henstridge <james@daa.com.au>
  4. (This is loosely based on gettext.pl in the GNU gettext distribution)
  5.  
  6. The best way to use it is like so:
  7.     import gettext
  8.     gettext.bindtextdomain(PACKAGE, LOCALEDIR)
  9.     gettext.textdomain(PACKAGE)
  10.     _ = gettext.gettext
  11.     print _('Hello World')
  12.  
  13. where PACKAGE is the domain for this package, and LOCALEDIR is usually
  14. '$prefix/share/locale' where $prefix is the install prefix.
  15.  
  16. If you have more than one catalog to use, you can directly create catalog
  17. objects.  These objects are created as so:
  18.     import gettext
  19.     cat = gettext.Catalog(PACKAGE, localedir=LOCALEDIR)
  20.     _ = cat.gettext
  21.     print _('Hello World')
  22.  
  23. The catalog object can also be accessed as a dictionary (ie cat['hello']).
  24.  
  25. There are also some experimental features.  You can add to the catalog, just
  26. as you would with a normal dictionary.  When you are finished, you can call
  27. its save method, which will create a new .mo file containing all the
  28. translations:
  29.     import gettext
  30.     cat = Catalog()
  31.     cat['Hello'] = 'konichiwa'
  32.     cat.save('./tmp.mo')
  33.  
  34. Once you have written an internationalized program, you can create a .po file
  35. for it with "xgettext --keyword=_ fillename ...".  Then do the translation and
  36. compile it into a .mo file, ready for use with this module.  Note that you
  37. will have to use C style strings (ie. use double quotes) for proper string
  38. extraction.
  39. """
  40. import os, string
  41.  
  42. prefix = '/usr/local'
  43. localedir = prefix + '/share/locale'
  44.  
  45. def _expandLang(str):
  46.     langs = [str]
  47.     # remove charset ...
  48.     if '.' in str:
  49.         langs.append(string.split(str, '.')[0])
  50.     # also add 2 character language code ...
  51.     if len(str) > 2:
  52.         langs.append(str[:2])
  53.     return langs
  54.  
  55. lang = []
  56. for env in 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG':
  57.     if os.environ.has_key(env):
  58.         lang = string.split(os.environ[env], ':')
  59.         lang = map(_expandLang, lang)
  60.         lang = reduce(lambda a, b: a + b, lang)
  61.         break
  62. if 'C' not in lang:
  63.     lang.append('C')
  64.  
  65. # remove duplicates
  66. i = 0
  67. while i < len(lang):
  68.     j = i + 1
  69.     while j < len(lang):
  70.         if lang[i] == lang[j]:
  71.             del lang[j]
  72.         else:
  73.             j = j + 1
  74.     i = i + 1
  75. del i, j
  76.  
  77. if os.environ.has_key('PY_XGETTEXT'):
  78.     xgettext = os.environ['PY_XGETTEXT']
  79. else:
  80.     xgettext = None
  81.  
  82. del os, string
  83.  
  84. error = 'gettext.error'
  85.  
  86. def _lsbStrToInt(str):
  87.     return ord(str[0]) + \
  88.            (ord(str[1]) << 8) + \
  89.            (ord(str[2]) << 16) + \
  90.            (ord(str[3]) << 24)
  91. def _intToLsbStr(int):
  92.     return chr(int         & 0xff) + \
  93.            chr((int >> 8)  & 0xff) + \
  94.            chr((int >> 16) & 0xff) + \
  95.            chr((int >> 24) & 0xff)
  96.  
  97. def _getpos(levels = 0):
  98.     """Returns the position in the code where the function was called.
  99.     The function uses some knowledge about python stack frames."""
  100.     import sys
  101.     # get access to the stack frame by generating an exception.
  102.     try:
  103.         raise RuntimeError
  104.     except RuntimeError:
  105.         frame = sys.exc_traceback.tb_frame
  106.     frame = frame.f_back # caller's frame
  107.     while levels > 0:
  108.         frame = frame.f_back
  109.         levels = levels - 1
  110.     return (frame.f_globals['__name__'],
  111.         frame.f_code.co_name,
  112.         frame.f_lineno)
  113.  
  114. class Catalog:
  115.     def __init__(self, domain=None, localedir=localedir):
  116.         self.domain = domain
  117.         self.localedir = localedir
  118.         self.cat = {}
  119.         if not domain: return
  120.         for self.lang in lang:
  121.             if self.lang == 'C':
  122.                 return
  123.             catalog = "%s//%s/LC_MESSAGES/%s.mo" % (
  124.                 localedir, self.lang, domain)
  125.             try:
  126.                 f = open(catalog, "rb")
  127.                 buffer = f.read()
  128.                 del f
  129.                 break
  130.             except IOError:
  131.                 pass
  132.         else:
  133.             return # assume C locale
  134.  
  135.         if _lsbStrToInt(buffer[:4]) != 0x950412de:
  136.             # magic number doesn't match
  137.             raise error, 'Bad magic number in %s' % (catalog,)
  138.  
  139.         self.revision = _lsbStrToInt(buffer[4:8])
  140.         nstrings = _lsbStrToInt(buffer[8:12])
  141.         origTabOffset  = _lsbStrToInt(buffer[12:16])
  142.         transTabOffset = _lsbStrToInt(buffer[16:20])
  143.         for i in range(nstrings):
  144.             origLength = _lsbStrToInt(buffer[origTabOffset:
  145.                              origTabOffset+4])
  146.             origOffset = _lsbStrToInt(buffer[origTabOffset+4:
  147.                              origTabOffset+8])
  148.             origTabOffset = origTabOffset + 8
  149.             origStr = buffer[origOffset:origOffset+origLength]
  150.         
  151.             transLength = _lsbStrToInt(buffer[transTabOffset:
  152.                               transTabOffset+4])
  153.             transOffset = _lsbStrToInt(buffer[transTabOffset+4:
  154.                               transTabOffset+8])
  155.             transTabOffset = transTabOffset + 8
  156.             transStr = buffer[transOffset:transOffset+transLength]
  157.             
  158.             self.cat[origStr] = transStr
  159.  
  160.     def gettext(self, string):
  161.         """Get the translation of a given string"""
  162.         if self.cat.has_key(string):
  163.             return self.cat[string]
  164.         else:
  165.             return string
  166.     # allow catalog access as cat(str) and cat[str] and cat.gettext(str)
  167.     __getitem__ = gettext
  168.     __call__ = gettext
  169.  
  170.     # this is experimental code for producing mo files from Catalog objects
  171.     def __setitem__(self, string, trans):
  172.         """Set the translation of a given string"""
  173.         self.cat[string] = trans
  174.     def save(self, file):
  175.         """Create a .mo file from a Catalog object"""
  176.         try:
  177.             f = open(file, "wb")
  178.         except IOError:
  179.             raise error, "can't open " + file + " for writing"
  180.         f.write(_intToLsbStr(0x950412de))    # magic number
  181.         f.write(_intToLsbStr(0))             # revision
  182.         f.write(_intToLsbStr(len(self.cat))) # nstrings
  183.  
  184.         oIndex = []; oData = ''
  185.         tIndex = []; tData = ''
  186.         for orig, trans in self.cat.items():
  187.             oIndex.append((len(orig), len(oData)))
  188.             oData = oData + orig + '\0'
  189.             tIndex.append((len(trans), len(tData)))
  190.             tData = tData + trans + '\0'
  191.         oIndexOfs = 20
  192.         tIndexOfs = oIndexOfs + 8 * len(oIndex)
  193.         oDataOfs = tIndexOfs + 8 * len(tIndex)
  194.         tDataOfs = oDataOfs + len(oData)
  195.         f.write(_intToLsbStr(oIndexOfs))
  196.         f.write(_intToLsbStr(tIndexOfs))
  197.         for length, offset in oIndex:
  198.             f.write(_intToLsbStr(length))
  199.             f.write(_intToLsbStr(offset + oDataOfs))
  200.         for length, offset in tIndex:
  201.             f.write(_intToLsbStr(length))
  202.             f.write(_intToLsbStr(offset + tDataOfs))
  203.         f.write(oData)
  204.         f.write(tData)
  205.  
  206. _cat = None
  207. _cats = {}
  208.  
  209. if xgettext:
  210.     class Catalog:
  211.         def __init__(self, domain, localedir):
  212.             self.domain = domain
  213.             self.localedir = localedir
  214.             self._strings = {}
  215.         def gettext(self, string):
  216.             # there is always one level of redirection for calls
  217.             # to this function
  218.             pos = _getpos(2) # get this function's caller
  219.             if self._strings.has_key(string):
  220.                 if pos not in self._strings[string]:
  221.                     self._strings[string].append(pos)
  222.             else:
  223.                 self._strings[string] = [pos]
  224.             return string
  225.         __getitem__ = gettext
  226.         __call__ = gettext
  227.         def __setitem__(self, item, data):
  228.             pass
  229.         def save(self, file):
  230.             pass
  231.         def output(self, fp):
  232.             import string
  233.             fp.write('# POT file for domain %s\n' % (self.domain,))
  234.             for str in self._strings.keys():
  235.                 pos = map(lambda x: "%s(%s):%d" % x,
  236.                       self._strings[str])
  237.                 pos.sort()
  238.                 length = 80
  239.                 for p in pos:
  240.                     if length + len(p) > 74:
  241.                         fp.write('\n#:')
  242.                         length = 2
  243.                     fp.write(' ')
  244.                     fp.write(p)
  245.                     length = length + 1 + len(p)
  246.                 fp.write('\n')
  247.                 if '\n' in str:
  248.                     fp.write('msgid ""\n')
  249.                     lines = string.split(str, '\n')
  250.                     lines = map(lambda x:
  251.                             '"%s\\n"\n' % (x,),
  252.                             lines[:-1]) + \
  253.                             ['"%s"\n' % (lines[-1],)]
  254.                     fp.writelines(lines)
  255.                 else:
  256.                     fp.write('msgid "%s"\n' % (str,))
  257.                 fp.write('msgstr ""\n')
  258.                 
  259.     import sys
  260.     if hasattr(sys, 'exitfunc'):
  261.         _exitchain = sys.exitfunc
  262.     else:
  263.         _exitchain = None
  264.     def exitfunc(dir=xgettext, _exitchain=_exitchain):
  265.         # actually output all the .pot files.
  266.         import os
  267.         for file in _cats.keys():
  268.             fp = open(os.path.join(dir, file + '.pot'), 'w')
  269.             cat = _cats[file]
  270.             cat.output(fp)
  271.             fp.close()
  272.         if _exitchain: _exitchain()
  273.     sys.exitfunc = exitfunc
  274.     del sys, exitfunc, _exitchain, xgettext
  275.  
  276. def bindtextdomain(domain, localedir=localedir):
  277.     global _cat
  278.     if not _cats.has_key(domain):
  279.         _cats[domain] = Catalog(domain, localedir)
  280.     if not _cat: _cat = _cats[domain]
  281.  
  282. def textdomain(domain):
  283.     global _cat
  284.     if not _cats.has_key(domain):
  285.         _cats[domain] = Catalog(domain)
  286.     _cat = _cats[domain]
  287.  
  288. def gettext(string):
  289.     if _cat == None: raise error, "No catalog loaded"
  290.     return _cat.gettext(string)
  291.  
  292. _ = gettext
  293.  
  294. def dgettext(domain, string):
  295.     if domain is None:
  296.         return gettext(string)
  297.     if not _cats.has_key(domain):
  298.         raise error, "Domain '" + domain + "' not loaded"
  299.     return _cats[domain].gettext(string)
  300.  
  301. def test():
  302.     import sys
  303.     global localedir
  304.     if len(sys.argv) not in (2, 3):
  305.         print "Usage: %s DOMAIN [LOCALEDIR]" % (sys.argv[0],)
  306.         sys.exit(1)
  307.     domain = sys.argv[1]
  308.     if len(sys.argv) == 3:
  309.         bindtextdomain(domain, sys.argv[2])
  310.     textdomain(domain)
  311.     info = gettext('')  # this is where special info is often stored
  312.     if info:
  313.         print "Info for domain %s, lang %s." % (domain, _cat.lang)
  314.         print info
  315.     else:
  316.         print "No info given in mo file."
  317.  
  318. if __name__ == '__main__':
  319.     test()
  320.  
  321.